﻿/*
# -*- coding: utf-8 -*-
# 注意：

# 若希望将本代码用于商业用途，需先联系作者并获得明确授权。
# 作者不对因使用本代码引发的任何直接或间接损失负责。
# 如有违反上述条款的行为，作者保留追究法律责任的权利。

# 免责声明：
# 请尊重知识产权，遵循相关法律法规。

# Notice:
# If you wish to use this code for commercial purposes, you must contact the author and obtain explicit authorization.
# The author is not responsible for any direct or indirect losses caused by the use of this code.
# If any of the above terms are violated, the author reserves the right to pursue legal action.
# Disclaimer:
# Please respect intellectual property rights and comply with relevant laws and regulations.

# Внимание:
# Если вы хотите использовать этот код в коммерческих целях, вам необходимо связаться с автором и получить явное разрешение.
# Автор не несет ответственности за любые прямые или косвенные убытки, возникшие в результате использования этого кода.
# В случае нарушения вышеуказанных условий автор оставляет за собой право на юридическое преследование.
# Отказ от ответственности:
# Пожалуйста, уважайте интеллектуальную собственность и соблюдайте соответствующие законы и нормативные акты.

# Attention :
# Si vous souhaitez utiliser ce code à des fins commerciales, vous devez contacter l'auteur et obtenir une autorisation explicite.
# L'auteur n'est pas responsable des pertes directes ou indirectes causées par l'utilisation de ce code.
# En cas de violation des conditions ci-dessus, l'auteur se réserve le droit de poursuivre une action en justice.
# Avertissement :
# Veuillez respecter les droits de propriété intellectuelle et vous conformer aux lois et réglementations en vigueur.

# 注意：

# 若希望将本代码用于商业用途，需先联系作者并获得明确授权。
# 作者不对因使用本代码引发的任何直接或间接损失负责。
# 如有违反上述条款的行为，作者保留追究法律责任的权利。

# 免责声明：
# 请尊重知识产权，遵循相关法律法规。

# Notice:
# If you wish to use this code for commercial purposes, you must contact the author and obtain explicit authorization.
# The author is not responsible for any direct or indirect losses caused by the use of this code.
# If any of the above terms are violated, the author reserves the right to pursue legal action.
# Disclaimer:
# Please respect intellectual property rights and comply with relevant laws and regulations.

# Внимание:
# Если вы хотите использовать этот код в коммерческих целях, вам необходимо связаться с автором и получить явное разрешение.
# Автор не несет ответственности за любые прямые или косвенные убытки, возникшие в результате использования этого кода.
# В случае нарушения вышеуказанных условий автор оставляет за собой право на юридическое преследование.
# Отказ от ответственности:
# Пожалуйста, уважайте интеллектуальную собственность и соблюдайте соответствующие законы и нормативные акты.

# Attention :
# Si vous souhaitez utiliser ce code à des fins commerciales, vous devez contacter l'auteur et obtenir une autorisation explicite.
# L'auteur n'est pas responsable des pertes directes ou indirectes causées par l'utilisation de ce code.
# En cas de violation des conditions ci-dessus, l'auteur se réserve le droit de poursuivre une action en justice.
# Avertissement :
# Veuillez respecter les droits de propriété intellectuelle et vous conformer aux lois et réglementations en vigueur.

# 注意：

# 若希望将本代码用于商业用途，需先联系作者并获得明确授权。
# 作者不对因使用本代码引发的任何直接或间接损失负责。
# 如有违反上述条款的行为，作者保留追究法律责任的权利。

# 免责声明：
# 请尊重知识产权，遵循相关法律法规。

# Notice:
# If you wish to use this code for commercial purposes, you must contact the author and obtain explicit authorization.
# The author is not responsible for any direct or indirect losses caused by the use of this code.
# If any of the above terms are violated, the author reserves the right to pursue legal action.
# Disclaimer:
# Please respect intellectual property rights and comply with relevant laws and regulations.

# Внимание:
# Если вы хотите использовать этот код в коммерческих целях, вам необходимо связаться с автором и получить явное разрешение.
# Автор не несет ответственности за любые прямые или косвенные убытки, возникшие в результате использования этого кода.
# В случае нарушения вышеуказанных условий автор оставляет за собой право на юридическое преследование.
# Отказ от ответственности:
# Пожалуйста, уважайте интеллектуальную собственность и соблюдайте соответствующие законы и нормативные акты.

# Attention :
# Si vous souhaitez utiliser ce code à des fins commerciales, vous devez contacter l'auteur et obtenir une autorisation explicite.
# L'auteur n'est pas responsable des pertes directes ou indirectes causées par l'utilisation de ce code.
# En cas de violation des conditions ci-dessus, l'auteur se réserve le droit de poursuivre une action en justice.
# Avertissement :
# Veuillez respecter les droits de propriété intellectuelle et vous conformer aux lois et réglementations en vigueur.

# 注意：

# 若希望将本代码用于商业用途，需先联系作者并获得明确授权。
# 作者不对因使用本代码引发的任何直接或间接损失负责。
# 如有违反上述条款的行为，作者保留追究法律责任的权利。

# 免责声明：
# 请尊重知识产权，遵循相关法律法规。

# Notice:
# If you wish to use this code for commercial purposes, you must contact the author and obtain explicit authorization.
# The author is not responsible for any direct or indirect losses caused by the use of this code.
# If any of the above terms are violated, the author reserves the right to pursue legal action.
# Disclaimer:
# Please respect intellectual property rights and comply with relevant laws and regulations.

# Внимание:
# Если вы хотите использовать этот код в коммерческих целях, вам необходимо связаться с автором и получить явное разрешение.
# Автор не несет ответственности за любые прямые или косвенные убытки, возникшие в результате использования этого кода.
# В случае нарушения вышеуказанных условий автор оставляет за собой право на юридическое преследование.
# Отказ от ответственности:
# Пожалуйста, уважайте интеллектуальную собственность и соблюдайте соответствующие законы и нормативные акты.

# Attention :
# Si vous souhaitez utiliser ce code à des fins commerciales, vous devez contacter l'auteur et obtenir une autorisation explicite.
# L'auteur n'est pas responsable des pertes directes ou indirectes causées par l'utilisation de ce code.
# En cas de violation des conditions ci-dessus, l'auteur se réserve le droit de poursuivre une action en justice.
# Avertissement :
# Veuillez respecter les droits de propriété intellectuelle et vous conformer aux lois et réglementations en vigueur.

# 注意：

# 若希望将本代码用于商业用途，需先联系作者并获得明确授权。
# 作者不对因使用本代码引发的任何直接或间接损失负责。
# 如有违反上述条款的行为，作者保留追究法律责任的权利。

# 免责声明：
# 请尊重知识产权，遵循相关法律法规。

# Notice:
# If you wish to use this code for commercial purposes, you must contact the author and obtain explicit authorization.
# The author is not responsible for any direct or indirect losses caused by the use of this code.
# If any of the above terms are violated, the author reserves the right to pursue legal action.
# Disclaimer:
# Please respect intellectual property rights and comply with relevant laws and regulations.

# Внимание:
# Если вы хотите использовать этот код в коммерческих целях, вам необходимо связаться с автором и получить явное разрешение.
# Автор не несет ответственности за любые прямые или косвенные убытки, возникшие в результате использования этого кода.
# В случае нарушения вышеуказанных условий автор оставляет за собой право на юридическое преследование.
# Отказ от ответственности:
# Пожалуйста, уважайте интеллектуальную собственность и соблюдайте соответствующие законы и нормативные акты.

# Attention :
# Si vous souhaitez utiliser ce code à des fins commerciales, vous devez contacter l'auteur et obtenir une autorisation explicite.
# L'auteur n'est pas responsable des pertes directes ou indirectes causées par l'utilisation de ce code.
# En cas de violation des conditions ci-dessus, l'auteur se réserve le droit de poursuivre une action en justice.
# Avertissement :
# Veuillez respecter les droits de propriété intellectuelle et vous conformer aux lois et réglementations en vigueur.

 */
// /*
// Created by Darsan 
// */

using System;
using System.Collections.Generic;
using System.Linq;
using TMPro;
using UnityEngine;

namespace LevelEditor
{
    public class GameBoard : Board
    {

        [SerializeField] private PolyLine _linePrefab;

        public bool Creating { get; set; } = true;
        public bool CanSave => !Creating;

        public Piece DraggingPiece { get; private set; }


        private readonly List<Piece> _linePoints = new List<Piece>();

        public IEnumerable<Piece> LinePoints => _linePoints;
        public Color LineColor { get; set; }

        private PolyLine _line;
        private readonly Stack<IAction> _undoStack = new Stack<IAction>();
        private readonly Stack<IAction> _redoStack = new Stack<IAction>();
        private Vector2Int _draggingPieceStartCoordinate;

        protected override void Awake()
        {
            base.Awake();
            Refresh();
        }

//        public void AddPiece(PieceData data)
//        {
//            var piece = Instantiate(piecePrefab);
//            AddPiece(piece, data.coordinate);
//            piece.Clicked += tile => OnBoardTileClicked((BoardTile) tile.Holder);
//        }




        public void SetUp(IEnumerable<Vector2Int> points,IEnumerable<int> line)
        {
            ResetBoard();
            var list = points.ToList();
            list.ForEach(p=> AddPiece(new PieceData
            {
                coordinate = p
            }));

            foreach (var p in line)
            {
                _linePoints.Add((Piece) this[list[p]].Tile);
            }

            Creating = false;
            UpdateLineRenderer();
        }


        private void UpdateLineRenderer()
        {
            if(_linePoints.Count==0)
            {
                if(_line)Destroy(_line.gameObject);
                return;
            }

            if (_line == null)
            {
                _line = Instantiate(_linePrefab);
                _line.Color = LineColor;
            }

            
            _line.Points = Creating ? _linePoints.Select(piece => (Vector2)piece.transform.position).Append(Camera.main.ScreenToWorldPoint(Input.mousePosition)) : _linePoints.Select(piece => (Vector2)piece.transform.position);
        }


        private void Update()
        {
            if (Creating)
                UpdateLineRenderer();



            if (_linePoints.Count > 0 && Creating && Input.GetMouseButtonDown(1))
            {
                Creating = false;
                _undoStack.Clear();
                UpdateLineRenderer();
            }
            else if (!Creating && Input.GetMouseButtonDown(0))
            {
                var col = Physics2D.OverlapPoint(Camera.main.ScreenToWorldPoint(Input.mousePosition), 1 << 9);
                Debug.Log(col);
                if (col)
                {
                    DraggingPiece = col.GetComponent<Piece>();
                    _draggingPieceStartCoordinate = DraggingPiece.Holder.LocalCoordinate;
                }
            }

            if (DraggingPiece && Input.GetMouseButton(0))
            {
                var boardTile = GetNearestBoardTile(Camera.main.ScreenToWorldPoint(Input.mousePosition));

                if (boardTile.Tile == null)
                {
                    ((BoardTile) DraggingPiece.Holder).Tile = null;
                    boardTile.Tile = DraggingPiece;
                    DraggingPiece.transform.position = boardTile.transform.position;
                }

                UpdateLineRenderer();
            }

            if (DraggingPiece && Input.GetMouseButtonUp(0))
            {
                AddToUndoStack(new PieceMoveUndo(this,_draggingPieceStartCoordinate,DraggingPiece.Holder.LocalCoordinate));
                DraggingPiece = null;
            }

            if (!Creating && Input.GetKeyDown(KeyCode.RightArrow))
            {
                MoveShape(Vector2Int.right);
            }

            if (!Creating && Input.GetKeyDown(KeyCode.LeftArrow))
            {
                MoveShape(Vector2Int.left);
            }

            if (!Creating && Input.GetKeyDown(KeyCode.UpArrow))
            {
                MoveShape(Vector2Int.up);
            }

            if (!Creating && Input.GetKeyDown(KeyCode.DownArrow))
            {
                MoveShape(Vector2Int.down);
            }

            if (Input.GetKeyDown(KeyCode.Z) && (Input.GetKey(KeyCode.LeftCommand) || Input.GetKey(KeyCode.LeftControl))&&Input.GetKey(KeyCode.LeftShift))
            {
                if (_redoStack.Count > 0) Redo();
            }
            else if (Input.GetKeyDown(KeyCode.Z) && (Input.GetKey(KeyCode.LeftCommand) || Input.GetKey(KeyCode.LeftControl)))
            {
                if (_undoStack.Count > 0) Undo();
            }
            
          
        }

        private void AddToUndoStack(IAction action)
        {
            _undoStack.Push(action);
            _redoStack.Clear();
        }


        private void MoveShape(Vector2Int vec,bool includeUndo=true)
        {
            var maxXBoardTile = BoardTiles.Where(b => b.Tile).OrderByDescending(b => b.LocalCoordinate.x).First();
            var minXBoardTile = BoardTiles.Where(b => b.Tile).OrderBy(b => b.LocalCoordinate.x).First();
            var xMov = Mathf.Clamp((vec.x>0? maxXBoardTile:minXBoardTile).LocalCoordinate.x+vec.x,-_rows/2,_rows/2) - (vec.x>0? maxXBoardTile:minXBoardTile).LocalCoordinate.x;

            var maxYBoardTile = BoardTiles.Where(b => b.Tile).OrderByDescending(b => b.LocalCoordinate.y).First();
            var minYBoardTile = BoardTiles.Where(b => b.Tile).OrderBy(b => b.LocalCoordinate.y).First();
            var yMov = Mathf.Clamp((vec.y > 0 ? maxYBoardTile : minYBoardTile).LocalCoordinate.y + vec.y, -_rows / 2,
                _rows / 2) - (vec.y > 0 ? maxYBoardTile : minYBoardTile).LocalCoordinate.y;

            var pieceVsCoordinate = Pieces.ToDictionary(p=>p,p=>p.Holder.LocalCoordinate + new Vector2Int(xMov,yMov));

            var pieces = Pieces.ToList();
            foreach (var piece in pieces)
            {
                MovePiece(piece,pieceVsCoordinate[piece]);
            }
            
            UpdateLineRenderer();
            if(includeUndo)
            AddToUndoStack(new ShapeMoveUndo(this,vec));
        }

        public BoardTile GetNearestBoardTile(Vector2 pos)
        {
            return BoardTiles.OrderBy(tile => ((Vector2) tile.transform.position - pos).magnitude).First();
        }

        protected override void OnBoardTileClicked(BoardTile tile)
        {
            if (!Intractable)
            {
                return;
            }

            if (Creating)
            {
                var addedPiece = false;
                if(!tile.Tile)
                {
                    AddPiece(new PieceData
                    {
                        coordinate = tile.LocalCoordinate
                    });
                    addedPiece = true;
                }


                if (_linePoints.Count == 0 || !HasLine(_linePoints.Last(), (Piece) tile.Tile))
                {
                    _linePoints.Add((Piece) tile.Tile);
                    AddToUndoStack(new AddLineUndo(this,tile.LocalCoordinate,addedPiece));
                    UpdateLineRenderer();
                }

            }
         

            base.OnBoardTileClicked(tile);
        }

        private bool HasLine(Piece firstPiece, Piece secondPiece)
        {
            var index = _linePoints.FindIndex(piece => piece == firstPiece);

            return index != -1 && (index < _linePoints.Count - 1 && _linePoints[index + 1] == secondPiece ||
                                   index > 0 && _linePoints[index - 1] == secondPiece);
        }

        public void Undo()
        {
            if(_undoStack.Count==0)
                return;

            var undoCommand = _undoStack.Pop();
            undoCommand.Undo();
            
            _redoStack.Push(undoCommand);
        }

        public void Redo()
        {
            if(_redoStack.Count==0)
                return;

            var action = _redoStack.Pop();
            action.Redo();
            
            _undoStack.Push(action);
            
            Debug.Log("Redo:"+_redoStack.Count);
        }

        public override void ResetBoard()
        {
            base.ResetBoard();
            _undoStack.Clear();
            Creating = true;
            _linePoints.Clear();
            if(_line)
            Destroy(_line.gameObject);
            _line = null;
        }

        public class ShapeMoveUndo:IAction
        {
            private readonly GameBoard _board;
            private readonly Vector2Int _moveDelta;

            public bool HasUndo { get; private set; }

            public ShapeMoveUndo(GameBoard board,Vector2Int moveDelta)
            {
                _board = board;
                _moveDelta = moveDelta;
            }
            public void Undo()
            {
                if (HasUndo)
                    throw new InvalidOperationException();
                
                _board.MoveShape(-_moveDelta,false);
                _board.UpdateLineRenderer();

                HasUndo = true;
            }

            public void Redo()
            {
                if (!HasUndo)
                    throw new InvalidOperationException();
                
                _board.MoveShape(_moveDelta,false);
                _board.UpdateLineRenderer();

                HasUndo = false;
            }
        }

        public class PieceMoveUndo : IAction
        {
            private readonly GameBoard _board;
            private readonly Vector2Int _lastCoordinate;
            private readonly Vector2Int _currentCoordinate;

            public bool HasUndo { get; private set; }

            public PieceMoveUndo(GameBoard board,Vector2Int lastCoordinate,Vector2Int currentCoordinate)
            {
                _board = board;
                _lastCoordinate = lastCoordinate;
                _currentCoordinate = currentCoordinate;
            }

            public void Undo()
            {
                if (HasUndo)
                    throw new InvalidOperationException();
                
                _board.MovePiece((Piece)_board[_currentCoordinate].Tile,_lastCoordinate);
                _board.UpdateLineRenderer();

                HasUndo = true;
            }

            public void Redo()
            {
                if (!HasUndo)
                    throw new InvalidOperationException();
                
                _board.MovePiece((Piece) _board[_lastCoordinate].Tile,_currentCoordinate);
                _board.UpdateLineRenderer();

                HasUndo = false;
            }
        }

        public class AddLineUndo : IAction
        {
            private readonly GameBoard _board;
            private readonly Vector2Int _coordinate;
            private readonly bool _newPiece;

            public bool HasUndo { get; private set; }

            public AddLineUndo(GameBoard board,Vector2Int coordinate,bool newPiece)
            {
                _board = board;
                _coordinate = coordinate;
                _newPiece = newPiece;
            }


            public void Undo()
            {
                if (HasUndo)
                    throw new InvalidOperationException();
                
                if (_board._linePoints.Last().Holder.LocalCoordinate != _coordinate)
                    throw new Exception();
                _board._linePoints.RemoveAtEnd();
                _board.UpdateLineRenderer();
                if (_newPiece)
                {
                    _board.RemovePiece((Piece) _board[_coordinate].Tile);
                }

                HasUndo = true;
            }

            public void Redo()
            {
                if (!HasUndo)
                    throw new InvalidOperationException();

                if (_newPiece)
                {
                    _board.AddPiece(new PieceData
                    {
                        coordinate = _coordinate
                    });
                }
                
                _board._linePoints.Add((Piece)_board[_coordinate].Tile);
                _board.UpdateLineRenderer();
                HasUndo = false;
            }
        }

       
    }

}

public interface IAction
{
    bool HasUndo { get; }
    void Undo();
    void Redo();
}

